package com.koc.robot;

import com.koc.robot.common.*;
import com.koc.robot.font.TrueTypeFont;

import android.opengl.GLES11;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

enum LineStyle
{
	LINE_ROUGH,
	LINE_SMOOTH
}

public class GraphicsHelper 
{
	public static float lineWidth = 1;
	public static LineStyle lineStyle = LineStyle.LINE_SMOOTH;
	public static int boundTexture = 0;
	
	public static TrueTypeFont currentFont = null;
	
	private static void polyline(FloatBuffer coords)
	{
		// two float buffer fro every line end-point
		ByteBuffer verticesVbb = ByteBuffer.allocateDirect(2 * Float.SIZE * coords.position());
		verticesVbb.order(ByteOrder.nativeOrder());
		FloatBuffer vertices = verticesVbb.asFloatBuffer();
		
		FloatBuffer overdraw = null;
		
		Vector p, q, r;
		boolean looping = (coords.get(0) == coords.get(coords.position() - 2)) && (coords.get(1) == coords.get(coords.position() - 1));
		
		float halfwidth = lineWidth / 2.f;
		float pixel_size = 1.f;
		float overdraw_factor = 0.0f;
		
		if (lineStyle == LineStyle.LINE_SMOOTH)
		{
			ByteBuffer overdrawVbb = ByteBuffer.allocateDirect(2 * Float.SIZE * (2 * coords.position() + 2));
			overdrawVbb.order(ByteOrder.nativeOrder());
			overdraw = overdrawVbb.asFloatBuffer();
			
			float m[] = new float[16];
			GLES11.glGetFloatv(GLES11.GL_MODELVIEW_MATRIX, m, 0);
			float det = m[0] * m[5] * m[10] + m[4] * m[9] * m[2] + m[8] * m[1] * m[6];
			det -= m[2] * m[5] * m[8] + m[6] * m[9] * m[0] + m[10] * m[1] * m[4];
			pixel_size = 1.0f / (float)Math.sqrt(det);
			
			
			overdraw_factor = pixel_size / halfwidth;
			halfwidth = Math.max(0.0f, halfwidth - .25f * pixel_size);
		}
		
		// get line vertex boundaries
		// if not looping, extend the line at the beginning, else use last point as 'p'
		r = new Vector(coords.get(0), coords.get(1));
		if (!looping)
			q = new Vector(r.x * 2 - coords.get(2), r.y * 2 - coords.get(3));
		else
			q = new Vector(coords.get(coords.position() - 4), coords.get(coords.position() - 3));
	
		for (int i = 0; i + 3 < coords.position(); i += 2)
		{
			p = q; q = r;
			r = new Vector(coords.get(i + 2), coords.get(i + 3));
			pushIntersectionPoints(vertices, overdraw, i, coords.position(), halfwidth, overdraw_factor, p, q, r);
		}
		
		// if not looping, extend the line at the end, else use first point as 'r'
		p = q; q = r;
		if (!looping)
			r = new Vector(r.x + (q.x - p.x), r.y + (q.y - p.y));
		else
			r = new Vector(coords.get(2), coords.get(3));
		
		pushIntersectionPoints(vertices, overdraw, coords.position() - 2, coords.position(), halfwidth, overdraw_factor, p, q, r);
		
		// end get line vertex boundaries
		
		int numberOfPoints = coords.position();
		vertices.position(0);
		
		// now, we draw the core line
		GLES11.glDisable(GLES11.GL_TEXTURE_2D);
		GLES11.glEnableClientState(GLES11.GL_VERTEX_ARRAY);
		GLES11.glVertexPointer(2, GLES11.GL_FLOAT, 0, vertices);
		GLES11.glDrawArrays(GLES11.GL_TRIANGLE_STRIP, 0, numberOfPoints);
		
		// draw the line halo (antialiasing)
		if (lineStyle == LineStyle.LINE_SMOOTH)
			draw_overdraw(overdraw, coords.position(), pixel_size, looping);
		
		GLES11.glDisableClientState(GLES11.GL_VERTEX_ARRAY);
		GLES11.glEnable(GLES11.GL_TEXTURE_2D);
		
		// clean up logic do not needed on Java because GC.
	}
	
	public static void rectangle(DrawMode mode, float x, float y, float w, float h)
	{
		quad(mode, x, y, x, y + h, x + w, y + h, x + w, y);
	}
	
	public static void quad(DrawMode mode, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
	{
		// create the FloatBuffer
		ByteBuffer vbb = ByteBuffer.allocateDirect(8 * Float.SIZE);
		vbb.order(ByteOrder.nativeOrder());
		FloatBuffer coords = vbb.asFloatBuffer();
		
		coords.put(x1); coords.put(y1); coords.put(x2); coords.put(y2);
		coords.put(x3); coords.put(y3); coords.put(x4); coords.put(y4);
		
		polygon(mode, coords);
	}
	
	public static void polygon(DrawMode mode, FloatBuffer coords)
	{
		// make a closed loop
		if (coords.position() >= 2)
		{
			coords.put(coords.get(0));
			coords.put(coords.get(1));
		}
		
		if (mode == DrawMode.DRAW_LINE)
		{
			polyline(coords);
		}
		else
		{
			int numberOfPoints = coords.position() / 2 - 1;
			
			coords.position(0);
			
			GLES11.glDisable(GLES11.GL_TEXTURE_2D);
			GLES11.glEnableClientState(GLES11.GL_VERTEX_ARRAY);
			
			GLES11.glVertexPointer(2, GLES11.GL_FLOAT, 0, coords);
			GLES11.glDrawArrays(GLES11.GL_TRIANGLE_FAN, 0, numberOfPoints);
			
			GLES11.glDisableClientState(GLES11.GL_VERTEX_ARRAY);
			GLES11.glEnable(GLES11.GL_TEXTURE_2D);
		}
	}
	
	private static void pushIntersectionPoints(FloatBuffer vertices, FloatBuffer overdraw, 
			int pos, int count, float hw, float overdraw_factor,
			Vector p, Vector q, Vector r)
	{
		// calculate line directions
		Vector s = q.subtract(p);
		Vector t = r.subtract(q);
		
		// calculate vertex displacement vectors
		Vector n1 = s.getNormal();
		Vector n2 = t.getNormal();
		n1.normalize();
		n2.normalize();
		float det_norm = n1.crossProductLen(n2);
		n1.multLocal(hw);
		n2.multLocal(hw);
		
		// line parallel -> assume intersection at displacement points
		if (Math.abs(det_norm) <= 0.03f)
		{
			// vertices1
			Vector vec1 = q.subtract(n2);
			vertices.put(2 * pos + 0, vec1.x);
			vertices.put(2 * pos + 1, vec1.y);
			// vertices2
			Vector vec2 = q.add(n2);
			vertices.put(2 * pos + 2, vec2.x);
			vertices.put(2 * pos + 3, vec2.y);
		}
		else
		{
			float det = s.crossProductLen(t);
			Vector d = n1.subtract(n2);
			Vector b = s.subtract(d);
			Vector c = s.add(d);
			float lambda = (b.crossProductLen(t)) / det;
			float mu = (c.crossProductLen(t)) / det;
			
			// ordering for GL_TRIANGLE_STRIP
			
			Vector vec1 = p.add(s.mult(mu)).subtract(n1);
			vertices.put(2 * pos + 0, vec1.x);
			vertices.put(2 * pos + 1, vec1.y);
			// vertices2
			Vector vec2 = p.add(s.mult(lambda)).add(n1);
			vertices.put(2 * pos + 2, vec2.x);
			vertices.put(2 * pos + 3, vec2.y);
		}
		
		if (overdraw != null)
		{
			// displacement of the overdraw vertices
			Vector x = new Vector(vertices.get(2 * pos) - q.x, vertices.get(2 * pos + 1) - q.y);
			x = x.mult(overdraw_factor);
			
			// point1
			overdraw.put(2 * pos + 0, vertices.get(2 * pos + 0));
			overdraw.put(2 * pos + 1, vertices.get(2 * pos + 1));
			// point2
			overdraw.put(2 * pos + 2, vertices.get(2 * pos + 0) + x.x);
			overdraw.put(2 * pos + 3, vertices.get(2 * pos + 1) + x.y);
			// point3
			overdraw.put(2 * (2 * count - pos - 2) + 0, vertices.get(2 * (pos + 1) + 0));
			overdraw.put(2 * (2 * count - pos - 2) + 1, vertices.get(2 * (pos + 1) + 1));
			// point4
			overdraw.put(2 * (2 * count - pos - 1) + 0, vertices.get(2 * (pos + 1) + 0) - x.x);
			overdraw.put(2 * (2 * count - pos - 1) + 1, vertices.get(2 * (pos + 1) + 1) - x.y);
		}
	}
	
	private static void draw_overdraw(FloatBuffer overdraw, int count, float pixel_size, boolean looping)
	{
		if (!looping)
		{
			Vector s = new Vector(overdraw.get(2) - overdraw.get(6), overdraw.get(3) - overdraw.get(7));
			s.normalize();
			s.multLocal(pixel_size);
			overdraw.put(2, (overdraw.get(2) + s.x));
			overdraw.put(3, (overdraw.get(3) + s.y));
			overdraw.put(2 * (2 * count - 1), (overdraw.get(2 * (2 * count - 1)) + s.x));
			overdraw.put(2 * (2 * count - 1) + 1, (overdraw.get(2 * (2 * count - 1) + 1) + s.y));
			
			Vector t = new Vector(overdraw.get(2 * (count - 1)) - overdraw.get(2 * (count - 3)), overdraw.get(2 * (count - 1) + 1) - overdraw.get(2 * (count - 3) + 1));
			t.normalize();
			t.multLocal(pixel_size);
			overdraw.put(2 * (count - 1), (overdraw.get(2 * (count - 1)) + t.x));
			overdraw.put(2 * (count - 1) + 1, (overdraw.get(2 * (count - 1) + 1) + t.y));
			overdraw.put(2 * (count + 1), (overdraw.get(2 * (count + 1)) + t.x));
			overdraw.put(2 * (count + 1) + 1, (overdraw.get(2 * (count + 1) + 1) + t.y));
			
			// we need to draw two more triangles to close the
			// overdraw at the line start
			overdraw.put(2 * (2 * count), overdraw.get(0));
			overdraw.put(2 * (2 * count) + 1, overdraw.get(1));
			overdraw.put(2 * (2 * count + 1), overdraw.get(2));
			overdraw.put(2 * (2 * count + 1) + 1, overdraw.get(3));
		}
		
		// prepare colors:
		// even indices in overdraw* point to inner vertices => alpha = current - alpha.
		// odd indices point to outer vertices => alpha = 0
		float[] c = new float[4];
		GLES11.glGetFloatv(GLES11.GL_CURRENT_COLOR, c, 0);
		
		ByteBuffer colorBuffer = ByteBuffer.allocateDirect((2 * count + 2) * 4);
		colorBuffer.order(ByteOrder.nativeOrder());
		for (int i = 0; i < 2 * count + 2; ++i)
		{
			colorBuffer.put(4 * i + 0, (byte)(c[0] * 255.0f));
			colorBuffer.put(4 * i + 1, (byte)(c[1] * 255.0f));
			colorBuffer.put(4 * i + 2, (byte)(c[2] * 255.0f));
			colorBuffer.put(4 * i + 3, (byte)(c[3] * 255.0f * (i % 2 == 0 ? 1 : 0)));
		}
		
		colorBuffer.position(0);
		overdraw.position(0);
		
		// draw faded out line halos
		GLES11.glEnableClientState(GLES11.GL_COLOR_ARRAY);
		GLES11.glColorPointer(4, GLES11.GL_UNSIGNED_BYTE, 0, colorBuffer);
		GLES11.glVertexPointer(2, GLES11.GL_FLOAT, 0, overdraw);
		GLES11.glDrawArrays(GLES11.GL_TRIANGLE_STRIP, 0, 2 * count + 2 * (looping ? 0 : 1));
		GLES11.glDisableClientState(GLES11.GL_COLOR_ARRAY);
		
		// restore color
		GLES11.glColor4f(c[0], c[1], c[2], c[3]);
		
		// do not need to delete because of GC
	}
	
	public static void bindTexture(int textureID)
	{
		if (textureID != boundTexture || textureID == 0)
		{
			boundTexture = textureID;
			GLES11.glBindTexture(GLES11.GL_TEXTURE_2D, textureID);
		}
	}
	
	public static void draw(Texture texture, float x, float y)
	{
		// bind the texture 
		int textureID = texture.getTextureID();
		
		bindTexture(textureID);
		
		GLES11.glPushMatrix();
		
		GLES11.glTranslatef(x, y, 0);
		
		GLES11.glEnableClientState(GLES11.GL_VERTEX_ARRAY);
		GLES11.glEnableClientState(GLES11.GL_TEXTURE_COORD_ARRAY);
		
		GLES11.glVertexPointer(2, GLES11.GL_FLOAT, 0, texture.getVerticesBuffer());
		GLES11.glTexCoordPointer(2, GLES11.GL_FLOAT, 0, texture.getCoordinatesBuffer());
		GLES11.glDrawArrays(GLES11.GL_TRIANGLE_FAN, 0, 4);
		
		GLES11.glDisableClientState(GLES11.GL_TEXTURE_COORD_ARRAY);
		GLES11.glDisableClientState(GLES11.GL_VERTEX_ARRAY);
		
		GLES11.glPopMatrix();
	}
	
	public static void setColor(Color c)
	{
		GLES11.glColor4f(c.R, c.G, c.B, c.A);
	}
	
	public static void setColor(float r, float g, float b, float a)
	{
		GLES11.glColor4f(r, g, b, a);
	}
	
	public static void setScissor(int x, int y, int width, int height)
	{
		GLES11.glEnable(GLES11.GL_SCISSOR_TEST);
		GLES11.glScissor(x, Global.screenheight - (y + height), width, height);
	}
	
	public static void setScissor()
	{
		GLES11.glDisable(GLES11.GL_SCISSOR_TEST);
	}
	
	public static void setFont(TrueTypeFont font)
	{
		currentFont = font;
	}
	
	public static void print(String text, float x, float y)
	{
		if (currentFont != null)
		{
			currentFont.print(text, x, y);
		}
	}
}
